/*
 * sysmo 服务器被监控客户端（节点）信息
 * Copyright (C) 2009  YuLicheng
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.sysmo.server.node;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.Date;
import java.util.List;
import java.util.Vector;
import javax.xml.parsers.DocumentBuilderFactory;
import org.sysmo.server.node.item.Item;
import org.sysmo.communication.CommunicationInterface;
import org.sysmo.communication.CommunicationProtocol;
import org.sysmo.db.DatabaseInterface;
import org.sysmo.db.DbItem;
import org.sysmo.server.node.item.ConnectedItem;
import org.sysmo.server.node.item.ItemEvent;
import org.sysmo.server.node.item.ItemExistsException;
import org.sysmo.server.node.item.ItemNotExistsException;
import org.sysmo.server.node.item.ReceivedResult;
import org.sysmo.server.node.item.Warning;
import org.sysmo.server.util.InstanceNotInitializedException;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;

/**
 *
 * 被监控的客户端（节点）信息模块。每个类实例对应于一个被监控的节点。此类代表的节点是
 * 已经加入监控列表且已与服务器建立连接的节点。
 *
 * @author YuLicheng
 * @see Node
 */
public class ConnectedNode extends AddedNode {
    
    private CommunicationInterface client;
    private boolean listeningFromClient;
    private Thread clientThread;

    /**
     * 创建已连接的监控列表内的节点实例。此方法会从数据库中读取此客户端节点下的所有监
     * 控项信息。
     *
     * @param stateListener 节点状态改变回调接口。
     * @param dbInterface 数据库访问接口。
     * @param client 客户端通讯连接。
     * @param nodeID 节点的客户端ID。
     * @throws NodeNotExistsException 客户端ID不存在。
     */
    public ConnectedNode(NodeStateChangeListener stateListener,
            DatabaseInterface dbInterface, CommunicationInterface client, 
            int nodeID) throws NodeNotExistsException {
        super(stateListener, dbInterface, nodeID);
        this.client = client;
    }

    
    /**
     * 建立和此客户端节点的通信通道。此方法将会创建一个新线程来等待并分发处理客户端发
     * 送的消息，老的线程将立即返回。新线程会一直运行，直到
     * {@link #closeConnection()}方法被调用。
     */
    public void connect() {
        if (node == null) {
            throw new InstanceNotInitializedException();
        }
        final ConnectedNode cnode = this;
        listeningFromClient = true;
        clientThread = new Thread() {
            @Override
            public void run() {
                try {
                    while (listeningFromClient) {
                        dispathMessage(client.getString());
                    }
                } catch (Exception e) {
                    client.closeConnection();
                    if (listeningFromClient) {
                        stateListener.connectedNodeDisconnected(cnode,
                                NodeStateChangeListener.CLIENT_CONNECTION_ABORT);
                    }
                }
            }
        };
        clientThread.start();
    }

    /**
     * 分发处理客户端发送的消息。
     *
     * @param message 消息内容。
     */
    private void dispathMessage(String message) {
        System.out.println("S:" + message);
        Document doc;
        try {
            InputStream is = new ByteArrayInputStream(message.getBytes("utf-8"));
            doc = DocumentBuilderFactory.newInstance().
                    newDocumentBuilder().parse(is);
        } catch (Exception e) {
            e.printStackTrace();
            return;
        }
        int type;
        try {
            type = CommunicationProtocol.getType(doc);
        } catch (Exception e) {
            e.printStackTrace();
            return;
        }
        if (type == 2) { //客户端即将关闭。
            closeConnection(false);
            return;
        }
        //剩余的消息都是附带监控值ID的。
        int iid;
        try {
            iid = CommunicationProtocol.getItemID(doc);
        } catch (Exception e) {
            e.printStackTrace();
            return;
        }
        try {
            if (type == 14) { //监控数据记录
                ConnectedItem citem = new ConnectedItem(dbInterface, client, iid);
                doSave(citem, doc);
            } else if (type == 17) { //非法的客户端ID
                ConnectedItem citem = new ConnectedItem(dbInterface, client, iid);
                citem.resyncWithClient(node.cid, node.name);
            } else {
                /*其他类型的消息都应该是客户端对服务器先前发出的消息的回应，交由监
                 控项回应消息处理方法进行处理*/
                ItemEvent.doNodeMessage(doc, iid, type);
            }
        } catch (ItemNotExistsException e) {
            //非法的监控项ID，可能为先前被删除的监控项。
            try {
                client.sendString(CommunicationProtocol.encapsulateWithItemID(
                        17, iid, ""));
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

    /**
     * 将客户端发送的监控记录存入数据库。此方法将所有的记录传送给客户点指定的监控项
     * 对象，由监控项对象将数据继续传递直至最后写入数据库。
     *
     * @param item 相应的监控项对象。
     * @param doc 记录数据Document。
     */
    private void doSave(ConnectedItem item, Document doc) {
        long dateTime = 0;
        NodeList nodes = doc.getElementsByTagName("time");
        if (nodes.getLength() > 0) {
            try {
                dateTime = Long.parseLong(nodes.item(0).getFirstChild().
                        getNodeValue());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        NodeList gNodes = doc.getElementsByTagName("graph");
        Vector<ReceivedResult> allRes = new Vector<ReceivedResult>();
        for (int gIndex = 0; gIndex < gNodes.getLength(); gIndex++) {
            org.w3c.dom.Node gNode = gNodes.item(gIndex);
            org.w3c.dom.Node gNameNode = gNode.getAttributes().getNamedItem("name");
            String graphName = "";
            if (gNameNode != null) {
                graphName = gNameNode.getNodeValue();
            }
            for (org.w3c.dom.Node vNode = gNode.getFirstChild(); vNode != null;
                    vNode = vNode.getNextSibling()) {
                if (!vNode.getNodeName().equals("value")) {
                    continue;
                }
                org.w3c.dom.Node vNameNode = vNode.getAttributes().
                        getNamedItem("name");
                String valueName = "";
                if (vNameNode != null) {
                    valueName = vNameNode.getNodeValue();
                }
                int resultValue = 0;
                String warning = null;
                for (org.w3c.dom.Node rNode = vNode.getFirstChild(); rNode != null;
                        rNode = rNode.getNextSibling()) {
                    if (rNode.getNodeName().equals("result")) {
                        try {
                            resultValue = Integer.parseInt(rNode.getFirstChild().
                                    getNodeValue());
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    } else if (rNode.getNodeName().equals("warning")) {
                        warning = rNode.getFirstChild().getNodeValue();
                    }
                }
                Warning wa = null;
                if (warning != null) {
                    wa = new Warning(dbInterface, warning, new Date(dateTime));
                    wa.setNodeID(node.cid);
                }
                allRes.add(new ReceivedResult(dbInterface, wa, getName(),
                        item.getName(), graphName, valueName, dateTime, 
                        resultValue));
            }
        }
        List<Item> allItems = getAllItems();
        for (Item citem : allItems) {
            ((ConnectedItem)citem).saveData(allRes);
        }
    }

    /**
     * 断开与客户端的连接。此方法会发送断开连接消息给客户端，然后通知在
     * {@link #connect}方法中创建的新线程结束监听客户端消息并断开连接。此方法返回
     * 后即可安全地销毁此客户端对象。
     *
     * @param sendShutdown 指定是否向客户端发送服务器即将停机的消息
     */
    public void closeConnection(boolean sendShutdown) {
        if (node == null) {
            throw new InstanceNotInitializedException();
        }
        listeningFromClient = false;
        if (sendShutdown) {
            try {
                client.sendString(CommunicationProtocol.encapsulate(3, ""));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        client.closeConnection();
        try {
            clientThread.join();
        } catch (Exception e) {
            e.printStackTrace();
        }
        stateListener.connectedNodeDisconnected(this,
                NodeStateChangeListener.CLIENT_SHUTDOWN);
    }

    /**
     * 增加一个新监控项并返回。新增加的监控项中没有任何图表。
     *
     * @param name 新监控项名称。
     * @return 新增加的监控项。
     * @throws ItemExistsException 若存在同名图表则抛出此异常。
     */
    public Item addItem(String name) throws ItemExistsException {
        if (node == null) {
            throw new InstanceNotInitializedException();
        }
        return new ConnectedItem(dbInterface, client, name, node.cid);
    }

    /**
     * 查询当前节点对象是否确实与对应的客户端成功创建安全的网络连接。
     *
     * @return true若已连接，false若没有连接。
     */
    @Override
    public boolean isConnected() {
        if (node == null) {
            throw new InstanceNotInitializedException();
        }
        return client.isConnected();
    }

    /**
     * 获得此节点中的所有监控项对象列表。此类返回的列表中的元素是监控值对应的
     * {@link ConnectedItem}对象实例。
     *
     * @return 此节点中的所有监控项的{@link ConnectedItem}实例组成的列表。
     */
    @Override
    public List<Item> getAllItems() {
        if (node == null) {
            throw new InstanceNotInitializedException();
        }
        Vector<Item> allItems = new Vector<Item>();
        try {
            Vector<DbItem> items = dbInterface.query_ItemTableOfClient(node.cid);
            for (DbItem item : items) {
                try {
                    allItems.add(new ConnectedItem(dbInterface, client, item.iid));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return allItems;
    }

    /**
     * 删除当前客户端。此方法会给客户端发送客户端节点被删除消息，然后调用基类的
     * {@link AddedNode#remove()}方法将此节点以及此节点的所有下属图表内容全部删除。
     */
    @Override
    public void remove() {
        if (node == null) {
            throw new InstanceNotInitializedException();
        }
        try {
            client.sendString(CommunicationProtocol.encapsulate(4, ""));
        } catch (Exception e) {
            e.printStackTrace();
        }
        super.remove();
    }

    /**
     * 获取此节点对应的客户端通讯协议接口。
     *
     * @return 通讯协议接口。
     */
    public CommunicationInterface getCommunicationInterface() {
        return client;
    }
}
